package prefix.transaction;

import java.math.BigDecimal;
import java.util.Date;

import javax.persistence.LockModeType;

import models.Transaction;
import models.Transaction.Status;
import models.Transaction.Type;
import models.WmTransaction;
import prefix.exception.TransactionException;
import prefix.exception.TransactionException.CODE;
import prefix.integration.wm.AbstractWalletManager.ExecutionStatus;
import prefix.util.JPAUtil;

public class BasicTransaction extends AbstractTransaction {

    @Override
    ExecutionStatus executeTransaction() throws TransactionException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    void beforeExecuteTransaction() {
        // TODO Auto-generated method stub
    }

    @Override
    void afterExecuteTransaction() throws TransactionException {
        // TODO Auto-generated method stub

    }

    @Override
    void reconcileTransaction() {
        // TODO Auto-generated method stub

    }

    @Override
    void beforeReconcile() {
        // TODO Auto-generated method stub

    }

    @Override
    void afterReconcile() {
        // TODO Auto-generated method stub

    }

    public static BasicTransaction executeNow(Integer transId) {

        try {
            if (transId == null)
                throw new TransactionException(CODE.INVALID_TRANSACTION_ID);
            JPAUtil.beginTransaction();
            Transaction dbtrans = JPAUtil.em().find(Transaction.class, transId, LockModeType.PESSIMISTIC_WRITE);
            if (dbtrans == null)
                throw new TransactionException(CODE.INVALID_TRANSACTION_ID);
            if (!dbtrans.getStatus().isInstatus(Transaction.Status.PENDING))
                throw new TransactionException(CODE.INVALID_TRANSACTION_STATUS);
            BasicTransaction trans = TransactionFactory.createTransaction(dbtrans);
            trans.beforeExecuteTransaction();
            Date now = new Date();
            if (now.after(trans.getDbTrans().getScheduleD()))
                trans.execute();
            JPAUtil.commitTransaction();
            return trans;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        } finally {
            JPAUtil.closeEM();
        }

    }

    public static BasicTransaction executeNow(Transaction trans) {

        try {
            if (trans == null)
                throw new TransactionException(CODE.INVALID_TRANSACTION_ID);
            JPAUtil.beginTransaction();
            if (!trans.getStatus().isInstatus(Transaction.Status.PENDING))
                throw new TransactionException(CODE.INVALID_TRANSACTION_STATUS);
            BasicTransaction bTrans = TransactionFactory.createTransaction(trans);
            bTrans.beforeExecuteTransaction();
            Date now = new Date();
            if (now.after(bTrans.getDbTrans().getScheduleD()))
                bTrans.execute();
            JPAUtil.em().merge(trans);
            JPAUtil.commitTransaction();
            return bTrans;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        } finally {
            JPAUtil.closeEM();
        }

    }

    public static BasicTransaction failTheTransaction(Integer transId) throws TransactionException {
        if (transId == null)
            throw new TransactionException(CODE.INVALID_TRANSACTION_ID);
        try {
            JPAUtil.beginTransaction();
            Transaction dbtrans = JPAUtil.em().find(Transaction.class, transId, LockModeType.PESSIMISTIC_WRITE);
            if (dbtrans == null)
                throw new TransactionException(CODE.INVALID_TRANSACTION_ID);
            if (!Transaction.Type.T1_WITHDRAWL.equals(dbtrans.getType()))
                throw new TransactionException(CODE.NO_NEED_FAIL);
            if (!dbtrans.getStatus().isInstatus(Transaction.Status.IN_REVIEW))
                throw new TransactionException(CODE.INVALID_TRANSACTION_STATUS);
            BasicTransaction trans = TransactionFactory.createTransaction(dbtrans);
            trans.fail();
            JPAUtil.commitTransaction();
            return trans;
        } catch (TransactionException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new TransactionException(CODE.ERROR_IN_ROLLBACK, e);
        } finally {
            JPAUtil.closeEM();
        }

    }

    public static BasicTransaction createTransaction(Type type, Integer initActor, Integer fromActor, Integer toActor,
            Integer fromAccount, Integer toAccount, double amount, Date scheduleDate, Integer orderId, Integer loanId) {
        BasicTransaction trans = TransactionFactory.createTransaction(type, initActor, fromActor, toActor, fromAccount,
                toAccount, amount, scheduleDate, orderId, loanId);
        try {
            JPAUtil.beginTransaction();
            trans.create();
            if (!Status.PENDING.equals(trans.dbTrans.getStatus()))
                return null;
            JPAUtil.em().persist(trans.dbTrans);
            JPAUtil.commitTransaction();
            return trans;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            JPAUtil.closeEM();
        }
    }

    private void fail() throws TransactionException {
        try {
            genericValidationOnFail();
            ExecutionStatus wmStatus = executeRollbackTransaction();
            if (wmStatus == null) {
                throw new TransactionException(CODE.ERROR_IN_WM);
            }
            this.dbTrans.setExtStatus(wmStatus.getState());
            this.dbTrans.setStatus(Status.FAILED);
            this.dbTrans.setUpdateD(new Date());
        } catch (TransactionException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new TransactionException(CODE.ERROR_IN_ROLLBACK, e);
        }
    }

    private void create() {
        try {
            Integer wmTransId = executeCreateTransaction();
            if (wmTransId == null) {
                throw new TransactionException(CODE.ERROR_IN_WM);
            }
            this.dbTrans.setExtId(wmTransId.toString());
        } catch (TransactionException e) {
            if (e.getCode().equals(TransactionException.CODE.NO_NEED_CREATION))
                return;
            // TODO Auto-generated catch block
            e.printStackTrace();
            if (e.getCode().is_m_persist()) {
                this.dbTrans.setStatus(Status.DISCARD);
            }
        } catch (Exception e) {
            this.dbTrans.setStatus(Status.FAILED);
            e.printStackTrace();
        }
    }

    private void execute() {
        try {
            genericValidation();

            ExecutionStatus wmStatus = this.executeTransaction();
            validateWMStatus(WmTransaction.Status.values()[wmStatus.getState()]);
            BigDecimal fromBalance = manager.getBalance(this.dbTrans.getFromAid());
            BigDecimal toBalance = manager.getBalance(this.dbTrans.getToAid());
            this.getDbTrans().setExecD(new Date());
            this.getDbTrans().setExtStatus(wmStatus.getState());
            this.getDbTrans().setExtId(wmStatus.getExtID());
            this.getDbTrans().setStatus(Transaction.Status.EXECUTED);
            this.getDbTrans().setFromActorBalance(fromBalance.doubleValue());
            this.getDbTrans().setToActorBalance(toBalance.doubleValue());
            this.afterExecuteTransaction();

        } catch (Exception e) {
            e.printStackTrace();
            if (e instanceof TransactionException) {
                TransactionException.CODE code = ((TransactionException) e).getCode();
                if (code.is_m_persist()) {
                    this.getDbTrans().setStatus(Transaction.Status.IN_REVIEW);
                    this.dbTrans.setUpdateD(new Date());
                }
            } else {
                this.getDbTrans().setStatus(Transaction.Status.IN_REVIEW);
                this.dbTrans.setUpdateD(new Date());
            }
        }
    }

    private void validateWMStatus(WmTransaction.Status status) throws TransactionException {
        if (status == null)
            throw new TransactionException(CODE.INVALID_WM_STATUS);
        switch (status) {
        case NEW:
            throw new TransactionException(CODE.INVALID_WM_STATUS, "wm status : " + status.toString());
        case PENDING:
            throw new TransactionException(CODE.INVALID_WM_STATUS, "wm status : " + status.toString());
        case EXECUTED:
            break;
        case FAILED:
            throw new TransactionException(CODE.INVALID_WM_STATUS, "wm status : " + status.toString());
        default:
            break;
        }
    }

    public void genericValidation() throws TransactionException {
        this.validateAmount();
        this.validation();
    }

    public void genericValidationOnFail() throws TransactionException {
        this.validateAmountOnFail();
        this.validation();
    }

    private void validation() throws TransactionException {
        this.validateStatus(this.getAcceptedStatus());
        this.validateType(this.getAcceptedTypes());
        this.validateDate();
        this.validateExtID();
        this.validateWallet();
    }

    private void validateExtID() throws TransactionException {

    }

    private void validateDate() throws TransactionException {
        Date scheduledDate = this.getDbTrans().getScheduleD();
        Date now = new Date();
        if (scheduledDate == null)
            throw new TransactionException(TransactionException.CODE.NULL_SCHDEULE_DATE);
        if (!scheduledDate.before(now))
            throw new TransactionException(TransactionException.CODE.TOO_EARLY_TO_EXECUTE);

    }

    private void validateType(Type... types) throws TransactionException {
        Type type = this.getDbTrans().getType();

        boolean inGroup = false;
        for (Type t : types) {
            if (t == type) {
                inGroup = true;
                break;
            }
        }
        if (!inGroup)
            throw new TransactionException(TransactionException.CODE.INVALID_TYPE);

    }

    protected void validateStatus(Status... statuses) throws TransactionException {
        Status status = this.getDbTrans().getStatus();
        boolean inGroup = false;
        for (Status t : statuses) {
            if (t == status) {
                inGroup = true;
                break;
            }
        }
        if (!inGroup)
            throw new TransactionException(TransactionException.CODE.INVALID_TRANSACTION_STATUS);
    }

    protected final Status[] getAcceptedStatus() {
        return new Status[] { Status.PENDING };
    }

    @Override
    void validateAmount() throws TransactionException {
        // TODO Auto-generated method stub

    }

    @Override
    Type[] getAcceptedTypes() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    Integer executeCreateTransaction() throws TransactionException {
        return null;
    }

    @Override
    ExecutionStatus executeRollbackTransaction() throws TransactionException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    void validateAmountOnFail() throws TransactionException {
        // TODO Auto-generated method stub

    }

    @Override
    void validateWallet() throws TransactionException {

    }

}
